package bsh;


import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import bsh.exception.CompilationFailedException;
import bsh.meta.JavaClassWrapper;
import bsh.meta.Scope;
import bsh.meta.ScopeType;
import bsh.meta.TypeWrapper;
import bsh.utils.reflection.BytecodeHelper;

/**
 * @author takeshi
 */
public class ForNodeCompiler implements NodeCompiler {

    private final BSHForStatement node;

    private final CompilationUnit unit;

    /**
     * @param node node
     * @param unit unit
     */
    public ForNodeCompiler(BSHForStatement node, CompilationUnit unit) {
        super();
        this.node = node;
        this.unit = unit;
    }

    /**
     * {@inheritDoc}
     */
    public void compile(MethodVisitor host, CompilationState state) throws CompilationFailedException {
        SimpleNode initializer = null, condition = null, update = null, body = null;
        int i = 0;
        if (this.node.hasForInit) {
            initializer = this.node.getChild(i++);
        }
        if (this.node.hasExpression) {
            condition = this.node.getChild(i++);
        }
        if (this.node.hasForUpdate) {
            update = this.node.getChild(i++);
        }
        if (i < this.node.jjtGetNumChildren()) {
            body = this.node.getChild(i);
        }
        Scope newScope = state.newScope(ScopeType.LOOP_BLOCK);
        Label beforeCondition = new Label();
        Label beforeUpdate = newScope.getStartLabel();
        this.compileNodeClearStack(host, state, initializer);
        if (update != null) {
            host.visitJumpInsn(Opcodes.GOTO, beforeCondition);
            newScope.installStartLabel(host, state.getUnit().options());
            this.unit.compileNode(update, host, state);
            CompilerImpl.popStackIfNeeded(host, state);
        } else {
            newScope.installStartLabel(host, state.getUnit().options());
        }
        host.visitLabel(beforeCondition);
        if (condition != null) {
            this.unit.compileNode(condition, host, state);
        } else {
            BytecodeHelper.pushBooleanValue(true, host);
            state.pushStack(boolean.class);
        }
        this.unit.getCompilerManager().coerceToType(host, state.peekStack(), JavaClassWrapper.BOOLEAN_WRAPPER, state);
        state.assertStackTopType(JavaClassWrapper.BOOLEAN_WRAPPER);
        state.popStack();
        host.visitJumpInsn(Opcodes.IFEQ, newScope.getEndLabel());
        this.compileNodeClearStack(host, state, body);
        host.visitJumpInsn(Opcodes.GOTO, beforeUpdate);
        newScope.installEndLabel(host, this.unit.options());
        state.popScope();
        //        
    }

    /**
     * Compila o node, fazendo pop no stack se necessario
     * @param host {@link MethodVisitor}
     * @param state {@link CompilationState}
     * @param body {@link SimpleNode}
     * @throws CompilationFailedException e
     */
    private void compileNodeClearStack(MethodVisitor host, CompilationState state, SimpleNode body) throws CompilationFailedException {
        if (body != null) {
            this.unit.compileNode(body, host, state);
            CompilerImpl.popStackIfNeeded(host, state);
        }
    }

    /**
     * {@inheritDoc}
     */
    public TypeWrapper nodeResultType(Scope scope) throws CompilationFailedException {
        return JavaClassWrapper.VOID_WRAPPER;
    }

}
